// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <iostream>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <string>
#include <vector>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/futures_product.h"
#include "coin2/exchange/huobi_futures/symbology/currency.h"

namespace coin2::exchange::huobi_futures::symbology {

using huobi::symbology::HuobiFuturesCurrency;

class HuobiFuturesProduct
    : public base::symbology::FuturesProduct<HuobiFuturesCurrency, HuobiFuturesProduct>,
      public base::symbology::ProductFromStrMixin<HuobiFuturesProduct> {
 public:
  using FuturesProduct = base::symbology::FuturesProduct<HuobiFuturesCurrency, HuobiFuturesProduct>;
  using ContractType = base::symbology::ContractType;
  using MarketType = base::market::MarketType;

 public:
  HuobiFuturesProduct(
      const HuobiFuturesCurrency& base,
      const HuobiFuturesCurrency& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : FuturesProduct(
            base,
            quote,
            native_product,
            native_subscription_symbol,
            base::market::ExchangeType::Huobi,
            contract_type,
            expiry_timestamp,
            resolve_timestamp) {}

 public:
  static std::unique_ptr<HuobiFuturesProduct> FromNativeStrImpl(
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    using base::symbology::IsDateStr;

    if (!(native_product.size() >= 6 &&
          native_product.size() <= 15)) {
      return {};
    }

    std::string expiry_str;
    std::unique_ptr<HuobiFuturesCurrency> base;
    std::unique_ptr<HuobiFuturesCurrency> quote;
    std::vector<std::string> splitted;
    splitted = Split(native_product, "-");
    if (splitted.size() == 2) {
      CHECK(splitted[1] == "USD" || splitted[1] == "usd" ||
            splitted[1] == "USDT" || splitted[1] == "usdt") << native_product;
      expiry_str = "PERPETUAL";
      base = HuobiFuturesCurrency::FromNativeStrNoThrow(splitted[0], resolve_timestamp);
      quote = HuobiFuturesCurrency::FromNativeStrNoThrow(splitted[1], resolve_timestamp);
    } else if (splitted.size() == 3) {
      CHECK(splitted[1] == "USD" || splitted[1] == "usd" ||
            splitted[1] == "USDT" || splitted[1] == "usdt") << native_product;
      base = HuobiFuturesCurrency::FromNativeStrNoThrow(splitted[0], resolve_timestamp);
      quote = HuobiFuturesCurrency::FromNativeStrNoThrow(splitted[1], resolve_timestamp);
      expiry_str = splitted[2];
    } else if (splitted.size() == 1) {
      std::vector<std::string> splitted2;
      splitted2 = Split(native_product, "_");

      if (splitted2.size() == 2) {
        if (splitted2[1] == "CW") {
          expiry_str = "WEEK";
        } else if (splitted2[1] == "NW") {
          expiry_str = "NEXT_WEEK";
        } else if (splitted2[1] == "CQ") {
          expiry_str = "QUARTER";
        } else if (splitted2[1] == "NQ") {
          expiry_str = "NEXT_QUARTER";
        }
        base = HuobiFuturesCurrency::FromNativeStrNoThrow(splitted2[0], resolve_timestamp);
      } else {
        expiry_str = std::string("20") + native_product.substr(native_product.length() - 6);
        base = HuobiFuturesCurrency::FromNativeStrNoThrow(
            native_product.substr(0, native_product.length() - 6),
            resolve_timestamp);
      }
      quote = HuobiFuturesCurrency::FromNativeStrNoThrow("USD", resolve_timestamp);
    } else {
      return {};
    }

    if (!base) return {};

    std::string native_product_tmp;
    std::string native_subscription_symbol;
    ContractType contract_type;
    int64_t expiry_timestamp;
    bool success = CalculateParams(
        *base,
        *quote,
        expiry_str,
        resolve_timestamp,
        &native_product_tmp,
        &native_subscription_symbol,
        &contract_type,
        &expiry_timestamp);
    if (success) {
      if (IsDateStr(expiry_str)) {
        DCHECK_EQ(native_product_tmp, native_product);
      }
      return std::make_unique<HuobiFuturesProduct>(
          *base,
          *quote,
          native_product,
          native_subscription_symbol,
          contract_type,
          expiry_timestamp,
          resolve_timestamp);
    } else {
      return {};
    }
  }

  static bool CalculateParams(
      const HuobiFuturesCurrency& base,
      const HuobiFuturesCurrency& quote,
      const std::string& expiry_str,
      const int64_t resolve_timestamp,
      std::string* native_product,
      std::string* native_subscription_symbol,
      ContractType* contract_type,
      int64_t* expiry_timestamp) {
    using base::symbology::ContractType;
    using base::symbology::ExchangeType;
    using base::symbology::FindExpiryFriday;
    using base::symbology::IsDateStr;
    using base::symbology::StringToContractType;
    using base::symbology::ToDateStr;

    static const int64_t expiry_time_of_day_nanosec = 8 * 3600 * 1e9;
    std::stringstream ss;

    // native_product
    ss.str(std::string());
    if (IsDateStr(expiry_str)) {
      bool found = false;
      for (auto type : {ContractType::WEEK, ContractType::NEXT_WEEK,
                        ContractType::QUARTER, ContractType::NEXT_QUARTER}) {
        auto timestamp = FindExpiryFriday(
            ExchangeType::Huobi, type, resolve_timestamp, expiry_time_of_day_nanosec);
        if (expiry_str == ToDateStr(timestamp)) {
          *contract_type = type;
          *expiry_timestamp = timestamp;
          ss << base.native_currency()
             << expiry_str.substr(2);
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    } else if (IsDateStr(std::string("20") + expiry_str)) {
      bool found = false;
      for (auto type : {ContractType::WEEK, ContractType::NEXT_WEEK,
                        ContractType::QUARTER, ContractType::NEXT_QUARTER}) {
        auto timestamp = FindExpiryFriday(
            ExchangeType::Huobi, type, resolve_timestamp, expiry_time_of_day_nanosec);
        if (std::string("20") + expiry_str == ToDateStr(timestamp)) {
          *contract_type = type;
          *expiry_timestamp = timestamp;
          ss << base.native_currency() + "-" + quote.native_currency() + "-"
             << expiry_str;
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    } else {
      auto type = StringToContractType(expiry_str);
      if (type) {
        *contract_type = type.value();
        if (*contract_type == ContractType::PERPETUAL) {
          *expiry_timestamp = FuturesProduct::PERPETUAL_TIMESTAMP;
          ss << base.native_currency(MarketType::Futures) << "-"
             << quote.native_currency(MarketType::Futures);
        } else if (quote.currency() == "USD") {
          *expiry_timestamp = FindExpiryFriday(
              ExchangeType::Huobi, *contract_type, resolve_timestamp, expiry_time_of_day_nanosec);
          ss << base.native_currency()
             << ToDateStr(*expiry_timestamp).substr(2);
        } else if (quote.currency() == "USDT") {
          *expiry_timestamp = FindExpiryFriday(
              ExchangeType::Huobi, *contract_type, resolve_timestamp, expiry_time_of_day_nanosec);
          ss << base.native_currency() << "-" << quote.native_currency() << "-"
             << ToDateStr(*expiry_timestamp).substr(2);
        } else {
          return false;
        }
      } else {
        return false;
      }
    }
    *native_product = ss.str();

    // native_subscription_symbol
    if (*contract_type == ContractType::PERPETUAL) {
      *native_subscription_symbol = *native_product;
    } else if (quote.currency() == "USD") {
      ss.str(std::string());
      std::string native_type;
      if (*contract_type == ContractType::WEEK) {
        native_type = "CW";
      } else if (*contract_type == ContractType::NEXT_WEEK) {
        native_type = "NW";
      } else if (*contract_type == ContractType::QUARTER) {
        native_type = "CQ";
      } else if (*contract_type == ContractType::NEXT_QUARTER) {
        native_type = "NQ";
      }
      ss << base.native_currency() << "_" << native_type;
      *native_subscription_symbol = ss.str();
    } else if (quote.currency() == "USDT") {
      *native_subscription_symbol = *native_product;
    } else {
      return false;
    }
    return true;
  }
};

}  // namespace coin2::exchange::huobi_futures::symbology
